package com.excilys.formation.project.controllers;

import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.Set;
import java.util.TreeSet;

import javax.validation.Valid;
import javax.validation.constraints.Min;

import org.joda.time.Interval;
import org.joda.time.YearMonth;
import org.joda.time.format.DateTimeFormat;
import org.joda.time.format.DateTimeFormatter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.ui.ModelMap;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

import com.excilys.formation.project.controllers.formdata.VirementData;
import com.excilys.formation.project.controllers.util.AuthentifiedClientManager;
import com.excilys.formation.project.entities.Client;
import com.excilys.formation.project.entities.Compte;
import com.excilys.formation.project.entities.Operation;
import com.excilys.formation.project.entities.Transaction;
import com.excilys.formation.project.services.CompteService;
import com.excilys.formation.project.services.TransactionService;

/**
 * Controleur en charge de l'affichage et la gestion des virements.
 * 
 * @author excilys
 * 
 */
@Controller
@RequestMapping(value = "/secure/virement")
public class VirementController {
	/**
	 * Nombre de virements à afficher par page.
	 */
	private static final int VIREMENTS_PAR_PAGE = 30;

	/**
	 * Une instance de compteService.
	 */
	@Autowired
	private CompteService compteService;

	/**
	 * Service qui fournira les clients.
	 */
	@Autowired
	private TransactionService transactionService;

	/**
	 * Gère la récupération du client en session.
	 */
	@Autowired
	private AuthentifiedClientManager authentifiedClientManager;

	/**
	 * Formulaire de création d'un nouveau virement.
	 * 
	 * @param model
	 *            instance de ModelMap
	 * @return chemin vers la jsp
	 */
	@RequestMapping(method = RequestMethod.GET)
	public String passerVirement(final ModelMap model) {
		Client connectedClient = authentifiedClientManager.getSessionClient();
		model.addAttribute("client", connectedClient);
		model.addAttribute("comptes", new TreeSet<Compte>(connectedClient.getComptes()));
		model.addAttribute("virement", new VirementData());

		return "secure/virement/new";
	}

	/**
	 * Formulaire de création d'un nouveau virement.
	 * 
	 * @param virementData
	 *            données du virement
	 * @param result
	 *            Résultat de la validation
	 * @param model
	 *            instance de ModelMap
	 * @return chemin vers la jsp
	 */
	// FIXME Empécher les virements vers le compte d'un autre client
	@RequestMapping(method = RequestMethod.POST)
	public String creerVirement(@ModelAttribute("virement") @Valid VirementData virementData, BindingResult result, final ModelMap model) {
		Client connectedClient = authentifiedClientManager.getSessionClient();

		Compte emetteur = compteService.getCompte(virementData.getEmetteur());
		Compte recepteur = compteService.getCompte(virementData.getRecepteur());
		if (emetteur != null && recepteur != null && !emetteur.getNumero().equals(recepteur.getNumero())
				&& emetteur.getClient().getId() == connectedClient.getId() && (virementData.getMontant() < emetteur.getSolde())
				&& (!result.hasErrors())) {
			transactionService.creerVirement(emetteur, recepteur, virementData.getMontant());
			return "redirect:virement/listvirement";
		}
		if (virementData.getMontant() > emetteur.getSolde()) {
			model.addAttribute("emetteurError", "Vous n'avez pas les fonds nécessaires pour effecteur ce virement.");
		}

		if (virementData.getEmetteur().equals(virementData.getRecepteur())) {
			model.addAttribute("recepteurError", "Doit être différent du compte emetteur");
		}

		model.addAttribute("error");
		model.addAttribute("virement", virementData);
		model.addAttribute("comptes", new ArrayList<Compte>(connectedClient.getComptes()));
		return "secure/virement/new";
	}

	/**
	 * Redirige vers une vue qui contient tous les virements emis par le client connecté.
	 * 
	 * @param model
	 *            Modèle qui sera renvoyé avec la vue.
	 * @return La vue et modèle, peuplé des données.
	 */
	@RequestMapping(value = "/listvirement")
	public String getListVirements(final ModelMap model) {
		return "redirect:listvirement/0";
	}

	/**
	 * Redirige vers une vue qui contient les virements emis par le client connecté.
	 * 
	 * @param offset
	 *            Décalage par rapport au virement le plus récent
	 * @param model
	 *            Modèle qui sera renvoyé avec la vue.
	 * @return La vue et modèle, peuplé des données.
	 */
	@RequestMapping(method = RequestMethod.GET, value = "/listvirement/{offset}")
	public String showOperations(@PathVariable @Min(0) int offset, Model model) {
		Client connectedClient = authentifiedClientManager.getSessionClient();
		Interval now = YearMonth.now().toInterval();
		Set<Transaction> virements = transactionService.getVirementsEmisByClientByTimeInterval(
				connectedClient.getId(), now, offset, VIREMENTS_PAR_PAGE);
		long nombreVirements = transactionService.getCountVirementsClientByTimeInterval(connectedClient.getId(), now);

		DateTimeFormatter dateFormatter = DateTimeFormat.forStyle("LS");
		dateFormatter = dateFormatter.withLocale(Locale.FRANCE);
		model.addAttribute("dateFormatter", dateFormatter);

		Integer precedent = null;
		Integer suivant = null;
		if (offset != 0) {
			if (offset > VIREMENTS_PAR_PAGE) {
				precedent = offset - VIREMENTS_PAR_PAGE;
			} else {
				precedent = 0;
			}
		}
		if ((offset + VIREMENTS_PAR_PAGE) < nombreVirements) {
			suivant = offset + VIREMENTS_PAR_PAGE;
		}
		model.addAttribute("precedent", precedent);
		model.addAttribute("suivant", suivant);

		model.addAttribute(connectedClient);
		model.addAttribute("virements", getValidVirementTransaction(virements));
		return "secure/virement/listview";
	}

	/**
	 * Renvoie une liste de VirementData prets à être affichés à partir d'un ensemble de virements. Seules transactions ayant 2 opérations de type
	 * virement dont une négative et une positive sont ajoutées à la liste.
	 * 
	 * @param virements
	 *            ensemble de virements à vérifier
	 * @return la liste des VirementData
	 */
	private List<VirementData> getValidVirementTransaction(Set<Transaction> virements) {
		List<VirementData> listVirements = new ArrayList<VirementData>();

		for (Transaction transaction : virements) {
			Set<Operation> operations = transaction.getOperations();
			if (operations.size() < 2) {
				continue;
			}
			Operation[] operationsPrincipales = new Operation[2];
			for (Operation operation : operations) {
				if (operation.getMontant() > 0
						&& ((operationsPrincipales[0] != null && operation.getMontant() > operationsPrincipales[0].getMontant())
						|| operationsPrincipales[0] == null)
						&& operation.getTypeOperation().getLibelle().equals("VIREMENT")) {
					operationsPrincipales[0] = operation;
				} else if (operation.getMontant() < 0
						&& ((operationsPrincipales[1] != null && operation.getMontant() < operationsPrincipales[0].getMontant())
						|| operationsPrincipales[1] == null)
						&& operation.getTypeOperation().getLibelle().equals("VIREMENT")) {
					operationsPrincipales[1] = operation;
				}
			}
			if (operationsPrincipales[0] != null & operationsPrincipales[1] != null) {
				VirementData virementData = new VirementData(operationsPrincipales[1].getCompte().getNumero(),
						operationsPrincipales[0].getCompte().getNumero(),
						operationsPrincipales[0].getMontant(),
						transaction.getDate());
				listVirements.add(virementData);
			}
		}
		return listVirements;
	}
}
